﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Accord.Statistics.Models.Fields.Learning;

namespace TimbreRecognition.Extraction
{
    public class FeatureNormalizer
    {
        private const double MarginFactor = 0.65;

        //singleton
        private static FeatureNormalizer instance;

        private double[] max;
        private double[] min;

        private FeatureNormalizer() {}

        public List<Feature> Normalize(List<Feature> entries)
        {
            if (max == null || min == null)
            {
                List<double[]> data = GetDataFromDataSet(entries);
                FillRanges(data);
            }

            List<Feature> normalized = new List<Feature>();

            foreach (Feature entry in entries)
            {
                Feature feature = (Feature) entry.Clone();
                feature.MFCC = Normalize(entry.MFCC);
                normalized.Add(feature);
            }

            return normalized;
        }

        public List<double[]> Normalize(IEnumerable<double[]> data)
        {
            if (max == null || min == null)
            {
                FillRanges(data.ToList());
            }

            List<double[]> normalizedList = new List<double[]>();

            foreach (double[] elements in data)
            {
                double[] normalized = Normalize(elements);
                normalizedList.Add(normalized);
            }

            return normalizedList;
        }

        private double[] Normalize(double[] elements)
        {
            double[] normalizedValues = new double[elements.Length];
            for (int i = 0; i < elements.Length; i++)
            {
                double range = max[i] - min[i];

                double element = elements[i];

                if (element > max[i])
                {
                    //element = max[i];
                    throw new ArgumentException("Element greater then max");
                }

                if (element < min[i])
                {
                   // element = min[i];
                    throw new ArgumentException("Element less then max");
                }

                double value = (element - min[i]) / range;
                normalizedValues[i] = value;
            }

            return normalizedValues;           
        }

        private void FillRanges(List<double[]> data)
        {
            max = new double[data[0].Length];
            min = new double[max.Length];

            for (int index = 0; index < data.Count; index++)
            {
                double[] elements = data[index];
                for (int j = 0; j < elements.Length; j++)
                {
                    double element = elements[j];
                    if (element > max[j] || (index == 0))
                    {
                        max[j] = element;
                    }

                    if (element < min[j] || (index == 0))
                    {
                        min[j] = element;
                    }
                }                
            }
            double maximum = max.Max();
            double minimum = min.Min();
            for (int i = 0; i < max.Length; i++)
            {
                max[i] += MarginFactor * Math.Abs(max[i] - min[i]);
              //  max[i] = maximum + 0.1 * Math.Abs(maximum - minimum);
            }

            for (int i = 0; i < max.Length; i++)
            {
               min[i] -= MarginFactor * Math.Abs(max[i] - min[i]);
              //  min[i] = minimum - 0.1 * Math.Abs(maximum - minimum);
            }
        }       

        private List<double[]> GetDataFromDataSet(List<Feature> dataSet)
        {
            List<double[]> data = new List<double[]>();
            foreach (Feature dataEntry in dataSet)
            {
                data.Add(dataEntry.MFCC);
            }

            return data;
        }

        public static FeatureNormalizer GetInstance()
        {
            if (instance == null)
            {
                 instance = new FeatureNormalizer();
            }

            return instance;
        }

        public List<DataItem> Normalize(List<DataItem> principleComponents)
        {
            if (max == null || min == null)
            {
                List<double[]> data = principleComponents.Select(o => o.DataSeries).ToList();
                FillRanges(data);
            }

            List<DataItem> normalized = new List<DataItem>();

            foreach (DataItem entry in principleComponents)
            {
                DataItem feature = new DataItem();
                feature.DataSeries = Normalize(entry.DataSeries);
                feature.ExpectedOutput = entry.ExpectedOutput;
                normalized.Add(feature);
            }

            return normalized;
        }

        //public List<DataItem> NormalizeOnTheirRange(List<DataItem> dataItems)
        //{
        //    int inputCount = dataItems[0].InputCount;
        //    double[] maxumum = new double[inputCount];
        //    double[] minimum = new double[inputCount];

        //    for (int i = 0; i < inputCount; i++)
        //    {
        //        maxumum[i] = dataItems.Max(o => o.DataSeries[i]);
        //        minimum[i] = dataItems.Min(o => o.DataSeries[i]);
        //    }

        //    List<DataItem> normalized = new List<DataItem>();
        //    foreach (DataItem dataItem in dataItems)
        //    {
        //        DataItem newDataItem = new DataItem();

        //        double[] normalizedValues = new double[inputCount];
        //        for (int i = 0; i < inputCount; i ++)
        //        {
        //            //normalize on range [0;1]
        //            double range = (maxumum[i] - minimum[i]);
        //            normalizedValues[i] = (dataItem.DataSeries[i] - minimum[i]) / range;
        //        }

        //        newDataItem.DataSeries = normalizedValues;
        //        newDataItem.ExpectedOutput = dataItem.ExpectedOutput;
        //        normalized.Add(newDataItem);
        //    }

        //    return normalized;
        //}

        public List<DataItem> NormalizeOnTheirRange(List<DataItem> dataItems)
        {
            double max = dataItems.Select(o => o.DataSeries).Max(o => o.Max());
            double min = dataItems.Select(o => o.DataSeries).Min(o => o.Min());

            double range = max - min;

            List<DataItem> normalized = new List<DataItem>();
            foreach (DataItem dataItem in dataItems)
            {
                DataItem newDataItem = new DataItem();
                newDataItem.DataSeries = dataItem.DataSeries.Select(o => (o - min) / range).ToArray();
                newDataItem.ExpectedOutput = dataItem.ExpectedOutput;
                normalized.Add(newDataItem);
            }

            return normalized;
        } 
    }
}
